﻿using System;
using System.Collections.Generic;
using System.Linq;

using mvcForum.Core;
using mvcForum.Core.Abstractions;
using mvcForum.Core.Events;
using mvcForum.Core.Interfaces.AddOns;
using mvcForum.Core.Interfaces.Events;
using mvcForum.Core.Interfaces.Search;
using mvcForum.Core.Interfaces.Data;
using mvcForum.Core.Interfaces.Services;

namespace mvcForum.SearchProvider.Lucene {

	public class LuceneContentChangeEventListener : IAsyncEventListener<NewPostEvent>,
													IAsyncEventListener<NewTopicEvent>,
													IAsyncEventListener<PostUpdatedEvent>,
													IAsyncEventListener<TopicUpdatedEvent>,
													IAsyncEventListener<PostFlagUpdatedEvent>,
													IAsyncEventListener<TopicFlagUpdatedEvent>,
													IAsyncEventListener<TopicSplitEvent>,
													IAsyncEventListener<TopicMergedEvent>,
													IAsyncEventListener<TopicMovedEvent> {

		private readonly IEnumerable<IIndexer> indexers;
		private readonly ILogger logger;
		private readonly IRepository<Post> postRepo;
		private readonly IRepository<Topic> topicRepo;
		private readonly IAsyncTask task;
		private readonly AsyncAddOnConfiguration<Indexer> config;

		public LuceneContentChangeEventListener(IEnumerable<IIndexer> indexers, ILogger logger, IRepository<Post> postRepo, IRepository<Topic> topicRepo, IAsyncTask task, AsyncAddOnConfiguration<Indexer> config) {
			this.indexers = indexers.Where(i => i.GetType() == typeof(Indexer));
			this.logger = logger;
			this.postRepo = postRepo;
			this.topicRepo = topicRepo;
			this.task = task;
			this.config = config;
		}

		#region Queue methods
		/// <summary>
		/// Method for queueing the updated post event for later/asynchronously handling
		/// </summary>
		/// <param name="payload"></param>
		public void Queue(PostUpdatedEvent payload) {
			this.logger.Log(EventType.Debug, String.Format("The updated post {0} is queued.", payload.PostId));
			this.task.Execute(this, payload, this.config.Delay);
		}

		/// <summary>
		/// Method for queueing the updated topic event for later/asynchronously handling
		/// </summary>
		/// <param name="payload"></param>
		public void Queue(TopicUpdatedEvent payload) {
			this.logger.Log(EventType.Debug, String.Format("The updated topic {0} is queued.", payload.TopicId));
			this.task.Execute(this, payload, this.config.Delay);
		}

		/// <summary>
		/// Method for queueing the new topic event for later/asynchronously handling
		/// </summary>
		/// <param name="payload"></param>
		public void Queue(NewTopicEvent payload) {
			this.logger.Log(EventType.Debug, String.Format("The new topic {0} is queued.", payload.TopicId));
			this.task.Execute(this, payload, this.config.Delay);
		}

		/// <summary>
		/// Method for queueing the topic split event for later/asynchronously handling
		/// </summary>
		/// <param name="payload"></param>
		public void Queue(TopicSplitEvent payload) {
			this.logger.Log(EventType.Debug, String.Format("The split topic {0} is queued.", payload.OriginalTopicId));
			this.task.Execute(this, payload, this.config.Delay);
		}

		/// <summary>
		/// Method for queueing the new post event for later/asynchronously handling
		/// </summary>
		/// <param name="payload"></param>
		public void Queue(NewPostEvent payload) {
			this.logger.Log(EventType.Debug, String.Format("The new post {0} is queued.", payload.PostId));
			this.task.Execute(this, payload, this.config.Delay);
		}

		public void Queue(PostFlagUpdatedEvent payload) {
			this.logger.Log(EventType.Debug, String.Format("The updated post {0} is queued.", payload.PostId));
			this.task.Execute(this, payload, this.config.Delay);
		}

		public void Queue(TopicFlagUpdatedEvent payload) {
			this.logger.Log(EventType.Debug, String.Format("The updated topic {0} is queued.", payload.TopicId));
			this.task.Execute(this, payload, this.config.Delay);
		}

		public void Queue(TopicMergedEvent payload) {
			this.logger.Log(EventType.Debug, String.Format("The merged topic {0} is queued.", payload.TopicId));
			this.task.Execute(this, payload, this.config.Delay);
		}

		public void Queue(TopicMovedEvent payload) {
			this.logger.Log(EventType.Debug, String.Format("The moved topic {0} is queued.", payload.TopicId));
			this.task.Execute(this, payload, this.config.Delay);
		}
		#endregion

		#region Handle methods
		public void Handle(Object payload) {
			if (payload is TopicUpdatedEvent) {
				this.Handle((TopicUpdatedEvent)payload);
			}
			else if (payload is PostUpdatedEvent) {
				this.Handle((PostUpdatedEvent)payload);
			}
			else if (payload is NewTopicEvent) {
				this.Handle((NewTopicEvent)payload);
			}
			else if (payload is NewPostEvent) {
				this.Handle((NewPostEvent)payload);
			}
			else if (payload is PostFlagUpdatedEvent) {
				this.Handle((PostFlagUpdatedEvent)payload);
			}
			else if (payload is TopicFlagUpdatedEvent) {
				this.Handle((TopicFlagUpdatedEvent)payload);
			}
			else if (payload is TopicMergedEvent) {
				this.Handle((TopicMergedEvent)payload);
			}
			else if (payload is TopicMovedEvent) {
				this.Handle((TopicMovedEvent)payload);
			}
			else {
				throw new ApplicationException("Unknown payload!");
			}
		}

		public void Handle(TopicMergedEvent payload) {
			// Let's get the merged topic.
			Core.Topic topic = this.topicRepo.Read(payload.TopicId);
			// Log info!
			this.logger.Log(EventType.Debug, String.Format("A topic '{1}' was merged, time to index it ({0}).", topic.Id, topic.Title));
			// Let's remove the old topic
			this.Remove(topic);
			// And reindex it with all the new content!
			this.Index(topic);
		}

		public void Handle(TopicMovedEvent payload) {
			// Let's get the moved topic.
			Core.Topic topic = this.topicRepo.Read(payload.TopicId);
			// Log info!
			this.logger.Log(EventType.Debug, String.Format("A topic '{1}' was moved, time to reindex it ({0}).", topic.Id, topic.Title));
			// Let's remove the old topic
			this.Remove(topic);
			// And reindex it in its new forum!
			this.Index(topic);
		}

		/// <summary>
		/// Method for handling the updated post event.
		/// </summary>
		/// <param name="payload">Any relevant info regarding the updated post.</param>
		public void Handle(PostUpdatedEvent payload) {
			// Let's get the updated post.
			Core.Post post = this.postRepo.Read(payload.PostId);
			// Log info!
			this.logger.Log(EventType.Debug, String.Format("A post '{1}' was updated, time to index it ({0}).", post.Id, post.Subject));
			// Has the post been deleted or put in quarantine?
			if (post.Flag == PostFlag.Deleted || post.Flag == PostFlag.Quarantined) {
				// Yes, let's remove it from the index!
				this.Remove(post);
			}
			else {
				// No, let's index it then!
				this.Index(post);
			}
		}

		/// <summary>
		/// Method for handling the updated topic event.
		/// </summary>
		/// <param name="payload">Any relevant info regarding the updated topic.</param>
		public void Handle(TopicUpdatedEvent payload) {
			// Let's get the updated topic.
			Core.Topic topic = this.topicRepo.Read(payload.TopicId);
			// Log info!
			this.logger.Log(EventType.Debug, String.Format("A topic '{1}' was updated, time to index it ({0}).", topic.Id, topic.Title));
			// Has the topic been deleted or put in quarantine?
			if (topic.Flag == TopicFlag.Deleted || topic.Flag == TopicFlag.Quarantined) {
				this.Remove(topic);
			}
			else {
				this.Index(topic);
			}
		}

		/// <summary>
		/// Method for handling the updated topic event.
		/// </summary>
		/// <param name="payload">Any relevant info regarding the updated topic.</param>
		public void Handle(TopicSplitEvent payload) {
			// Let's get the original topic.
			Core.Topic originalTopic = this.topicRepo.Read(payload.OriginalTopicId);
			// Let's get the new topic.
			Core.Topic newTopic = this.topicRepo.Read(payload.NewTopicId);
			// Log info!
			this.logger.Log(EventType.Debug, String.Format("A new topic '{1}', was created by splitting '{0}' in two.", originalTopic.Title, newTopic.Title));

			this.Remove(originalTopic);
			if (originalTopic.Flag != TopicFlag.Deleted &&
				originalTopic.Flag != TopicFlag.Quarantined &&
				originalTopic.Flag != TopicFlag.Moved) {

				this.Index(originalTopic);
			}

			if (newTopic.Flag != TopicFlag.Deleted &&
				newTopic.Flag != TopicFlag.Quarantined &&
				newTopic.Flag != TopicFlag.Moved) {

				this.Index(newTopic);
			}
		}

		/// <summary>
		/// Method for handling the new topic event.
		/// </summary>
		/// <param name="payload">Any relevant info regarding the new topic.</param>
		public void Handle(NewTopicEvent payload) {
			// Let's get the new topic.
			Core.Topic topic = this.topicRepo.Read(payload.TopicId);
			// Log info!
			this.logger.Log(EventType.Debug, String.Format("A new topic '{1}', was created, time to index it ({0}).", topic.Id, topic.Title));
			// Has the topic been deleted or put in quarantine?
			if (topic.Flag != TopicFlag.Quarantined && topic.Flag != TopicFlag.Deleted) {
				this.Index(topic);
			}
		}

		/// <summary>
		/// Method for handling the new post event.
		/// </summary>
		/// <param name="payload">Any relevant infor regarding the new post.</param>
		public void Handle(NewPostEvent payload) {
			// Let's get the new post!
			Core.Post post = this.postRepo.Read(payload.PostId);
			// Log info!
			this.logger.Log(EventType.Debug, String.Format("A new post '{1}', was created, time to index it ({0}).", post.Id, post.Subject));
			// Has the post been deleted or put in quarantine?
			if (post.Flag != PostFlag.Quarantined && post.Flag != PostFlag.Deleted) {
				this.Index(post);
			}
		}

		public void Handle(PostFlagUpdatedEvent payload) {
			// Let's get the updated post!
			Core.Post post = this.postRepo.Read(payload.PostId);

			if (post.Flag != payload.OriginalFlag) {
				if (post.Flag == PostFlag.None) {
					// Now it's visible!
					this.Index(post);
				}
				else {
					// Not visible, let's remove it from the index!
					this.Remove(post);
				}
			}
		}

		public void Handle(TopicFlagUpdatedEvent payload) {
			// Let's get the updated topic!
			Core.Topic topic = this.topicRepo.Read(payload.TopicId);

			if (topic.Flag != payload.OriginalFlag) {
				if (topic.Flag == TopicFlag.None || topic.Flag == TopicFlag.Locked) {
					// Now it's visible!
					this.Index(topic);
				}
				else {
					// Not visible, let's remove it from the index!
					this.Remove(topic);
				}
			}
		}
		#endregion

		private void Index(Post post) {
			foreach (IIndexer idx in this.indexers) {
				// Let's index it!
				idx.Index(post);
			}
		}

		private void Index(Topic topic) {
			foreach (IIndexer idx in this.indexers) {
				// Let's index it!
				idx.Index(topic);
			}
		}

		private void Remove(Post post) {
			foreach (IIndexer idx in this.indexers) {
				// Let's remove it!
				idx.Remove(post);
			}
		}

		private void Remove(Topic topic) {
			foreach (IIndexer idx in this.indexers) {
				// Let's remove it!
				idx.Remove(topic);
			}
		}

		/// <summary>
		/// True if this handler should run asynchronously.
		/// </summary>
		public Boolean RunAsynchronously {
			get {
				return this.config.RunAsynchronously;
			}
		}

		/// <summary>
		/// The priority of this handler.
		/// </summary>
		public Byte Priority {
			get {
				return (Byte)EventPriority.Lowest;
			}
		}

		public Boolean UniqueEvent {
			get {
				return false;
			}
		}
	}
}